#pragma once

#include <boost/asio.hpp>
#include <chrono>
#include <functional>
#include <map>
#include <vector>

#include "crow/logging.h"

namespace crow {
namespace detail {
///
/// A class for scheduling functions to be called after a specific amount
/// of ticks. A tick is equal to 1 second.
///
class task_timer
{
 public:
  using task_type = std::function<void()>;
  using identifier_type = size_t;

 private:
  using clock_type = std::chrono::steady_clock;
  using time_type = clock_type::time_point;

 public:
  task_timer(boost::asio::io_service& io_service)
      : io_service_(io_service), deadline_timer_(io_service_)
  {
    deadline_timer_.expires_from_now(boost::posix_time::seconds(1));
    deadline_timer_.async_wait(
        std::bind(&task_timer::tick_handler, this, std::placeholders::_1));
  }

  ~task_timer() { deadline_timer_.cancel(); }

  void cancel(identifier_type id)
  {
    tasks_.erase(id);
    CROW_LOG_DEBUG << "task_timer cancelled: " << this << ' ' << id;
  }

  ///
  /// Schedule the given task to be executed after the default amount of
  /// ticks.
  ///
  /// \return identifier_type Used to cancel the thread.
  /// It is not bound to this task_timer instance and in some cases could lead to
  /// undefined behavior if used with other task_timer objects or after the task
  /// has been successfully executed.
  ///
  identifier_type schedule(const task_type& task)
  {
    tasks_.insert(
        {++highest_id_,
         {clock_type::now() + std::chrono::seconds(get_default_timeout()),
          task}});
    CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' << highest_id_;
    return highest_id_;
  }

  ///
  /// Schedule the given task to be executed after the given time.
  ///
  /// \param timeout The amount of ticks (seconds) to wait before execution.
  ///
  /// \return identifier_type Used to cancel the thread.
  /// It is not bound to this task_timer instance and in some cases could lead to
  /// undefined behavior if used with other task_timer objects or after the task
  /// has been successfully executed.
  ///
  identifier_type schedule(const task_type& task, std::uint8_t timeout)
  {
    tasks_.insert({++highest_id_,
                   {clock_type::now() + std::chrono::seconds(timeout), task}});
    CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' << highest_id_;
    return highest_id_;
  }

  ///
  /// Set the default timeout for this task_timer instance. (Default: 5)
  ///
  /// \param timeout The amount of ticks (seconds) to wait before execution.
  ///
  void set_default_timeout(std::uint8_t timeout) { default_timeout_ = timeout; }

  ///
  /// Get the default timeout. (Default: 5)
  ///
  std::uint8_t get_default_timeout() const { return default_timeout_; }

 private:
  void process_tasks()
  {
    time_type current_time = clock_type::now();
    std::vector<identifier_type> finished_tasks;

    for (const auto& task : tasks_) {
      if (task.second.first < current_time) {
        (task.second.second)();
        finished_tasks.push_back(task.first);
        CROW_LOG_DEBUG << "task_timer called: " << this << ' ' << task.first;
      }
    }

    for (const auto& task : finished_tasks) tasks_.erase(task);

    // If no task is currently scheduled, reset the issued ids back to 0.
    if (tasks_.empty()) highest_id_ = 0;
  }

  void tick_handler(const boost::system::error_code& ec)
  {
    if (ec) return;

    process_tasks();

    deadline_timer_.expires_from_now(boost::posix_time::seconds(1));
    deadline_timer_.async_wait(
        std::bind(&task_timer::tick_handler, this, std::placeholders::_1));
  }

 private:
  std::uint8_t default_timeout_{5};
  boost::asio::io_service& io_service_;
  boost::asio::deadline_timer deadline_timer_;
  std::map<identifier_type, std::pair<time_type, task_type>> tasks_;

  // A continuosly increasing number to be issued to threads to identify them.
  // If no tasks are scheduled, it will be reset to 0.
  identifier_type highest_id_{0};
};
}  // namespace detail
}  // namespace crow
